/*
 * arch/arm/mach-hisi/include/mach/uncompress.h
 *
 * Copyright (C)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * This work is licensed under the terms of the GNU GPL, version 2.  See
 * the COPYING file in the top-level directory.
 */
#ifndef _MACH_HISI_UNCOMPRESS_H_
#define _MACH_HISI_UNCOMPRESS_H_

#include <linux/of.h>
#include "../../../../../drivers/of/of_private.h"

#include  "serial_synopsys.h"
#include  "be8_compatible.h"

#ifdef CONFIG_RTOS_HAL_CHIP_HI5651T_SUPPORT
#include <../../../boot/compressed/hi5651t.h>
#endif

#ifdef CONFIG_RTOS_HAL_INVALIDATE_BTB
#include <../../../boot/compressed/flag_cmdline.h>
#endif

#include <linux/amba/serial.h>

unsigned int hisi_uart0_base;
unsigned int debug_ll;
extern unsigned char *output_data;
unsigned int uart_type;

enum {
	UART_8250,
	UART_PL011,
	UART_16550,
};

static const struct of_device_id uart_node_matches[] = {
	{ .compatible = "snps,dw-apb-uart", .data = (void *)UART_8250 },
	{ .compatible = "arm,pl011", .data = (void *)UART_PL011 },
	{ .compatible = "fsl,ns16550", .data = (void *)UART_16550 },
	{ .compatible = "ns16550", .data = (void *)UART_16550 },
	{}
};

static void flush(void)
{
	int l_stat;

	if (hisi_uart0_base == 0)
		return;

	switch ((int)uart_node_matches[uart_type].data) {
	case UART_8250:
	case UART_16550:
		IO_READ((hisi_uart0_base + SYNOPSYS_UART_LSR), l_stat);

		while ((l_stat & SYNOPSYS_UART_LSR_THRE) == 0)
			IO_READ((hisi_uart0_base + SYNOPSYS_UART_LSR), l_stat);

		break;
	case UART_PL011:
		IO_READ((hisi_uart0_base + UART01x_FR), l_stat);

		while ((l_stat & UART01x_FR_BUSY) == 1)
			IO_READ((hisi_uart0_base + UART01x_FR), l_stat);

		break;
	default:
		break;
	}

	barrier();
}

static void uart_transmit_onebyte(unsigned int addr, unsigned char data)
{
	int l_stat;

	switch ((int)uart_node_matches[uart_type].data) {
	case UART_8250:
	case UART_16550:
		IO_READ((addr + SYNOPSYS_UART_LSR), l_stat);

		while ((l_stat & SYNOPSYS_UART_LSR_THRE) == 0)
			IO_READ((addr + SYNOPSYS_UART_LSR), l_stat);

		io_write((addr + SYNOPSYS_UART_DR), data);

		break;
	case UART_PL011:
		IO_READ((addr + UART01x_FR), l_stat);

		while ((l_stat & UART01x_FR_BUSY) == 1)
			IO_READ((addr + UART01x_FR), l_stat);

		*(unsigned char *)(addr + UART01x_DR) = data;
		break;
	default:
		break;
	}
}

static void putc_hisi(const char ch)
{
	if (hisi_uart0_base == 0)
		return;

	if (ch == '\n')
		uart_transmit_onebyte(hisi_uart0_base, '\r');
	uart_transmit_onebyte(hisi_uart0_base, ch);
}

void hisi_putstr(const char *s)
{
	if (!debug_ll)
		return;

	while (*s)
		putc_hisi(*s++);
}

void print_number(unsigned int num)
{
	unsigned int i;
	unsigned int k;
	unsigned char a[11];

	a[0] = '0';
	a[1] = 'x';
	for (k = 9, i = 0; i < 8 ; i++, k--) {
		unsigned char uc_num_temp;

		uc_num_temp = (unsigned char)((num >> (i * 4)) & 0x0f);
		uc_num_temp += 0x30;
		if (uc_num_temp >= 0x3a)
			uc_num_temp += 7;
		a[k] = uc_num_temp;
	}
	a[10] = 0;

	hisi_putstr(a);
}

#ifdef CONFIG_ARM_APPENDED_DTB
void *__dtb_addr;
#include <linux/libfdt.h>

#if defined(CONFIG_RTOS_UART_EARLY_INIT)
#define UART_BUSYWAIT_MAXCIRCLE		4000000

static void uart_init_early_8250(void)
{
	volatile unsigned char value_t;
	unsigned int i;

	i = 0;
	do {
		*((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_LCR)) =
				(SYNOPSYS_UART_LCR_DLAB | SYNOPSYS_UART_LCR_DLS8);
		asm("dsb");
		value_t = *((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_LCR));

		/* To avoid dead circle when uart is busy */
		if (i > UART_BUSYWAIT_MAXCIRCLE)
			goto uart_irq_disable;

		i++;
	} while (!(value_t & SYNOPSYS_UART_LCR_DLAB));

	*((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_FCR)) =
		(SYNOPSYS_UART_FCR_EN | SYNOPSYS_UART_FCR_RCVR | SYNOPSYS_UART_FCR_XMIT);

	i = 0;
	do {
		*((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_LCR)) =
				SYNOPSYS_UART_LCR_DLS8;
		asm("dsb");
		value_t = *((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_LCR));

		/* To avoid dead circle when uart is busy */
		if (i > UART_BUSYWAIT_MAXCIRCLE)
			goto uart_irq_disable;

		i++;
	} while (value_t & SYNOPSYS_UART_LCR_DLAB);

uart_irq_disable:
	*((volatile unsigned char *)(uintptr_t)(hisi_uart0_base + SYNOPSYS_UART_IER)) = 0x00;
}

static void uart_init_early(void *dtb_address, int node)
{
	int size;
	const void *data = NULL;
	unsigned int uart_init_enabled = 0;

	if (hisi_uart0_base == 0)
		return;

	data = fdt_getprop(dtb_address, node, "uart_early_init", &size);
	if (data)
		uart_init_enabled = fdt32_to_cpu(*(fdt32_t *)data);

	if (!IS_ENABLED(CONFIG_RTOS_OPTIMIZE_ONT) && (uart_init_enabled == 0))
		return;
	/*
	 * this for ont boards such as 511x opened CONFIG_RTOS_OPTIMIZE_ONT
	 * and boards defined uart_early_init property in dtb such as 1210
	 */
	if (uart_node_matches[uart_type].data == (void *)UART_8250 ||
		uart_node_matches[uart_type].data == (void *)UART_16550)
		uart_init_early_8250();
}
#endif

static int get_addr_cells(const void * const dtb_addr, int offset)
{
	const unsigned int *data = NULL;
	int parent;

	do {
		parent = fdt_parent_offset(dtb_addr, offset);
		if (parent >= 0)
			offset = parent;

		data = fdt_getprop(dtb_addr, offset, "#address-cells", NULL);
		if (data)
			return fdt32_to_cpu(*data);
	} while (parent >= 0);

	return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}

static void find_uart_info(void *dtb_address)
{
	int node;
	int size;
	const void *data = NULL;
	unsigned int n_addr_cells;

	node = fdt_node_offset_by_prop_value(dtb_address, -1, "bus_id", "uart0", sizeof("uart0"));
	if (node >= 0) {
		for (uart_type = 0; uart_type < ARRAY_SIZE(uart_node_matches); uart_type++) {
			if (fdt_node_check_compatible(dtb_address, node,
				uart_node_matches[uart_type].compatible) == 0)
				break;
		}

		if (uart_type >= ARRAY_SIZE(uart_node_matches))
			return;

		data = fdt_getprop(dtb_address, node, "reg", &size);
		if (data) {
			n_addr_cells = get_addr_cells(dtb_address, node);
			if (n_addr_cells == 1)
				hisi_uart0_base = fdt32_to_cpu(*(fdt32_t *)data);
			else if (n_addr_cells == 2)
				/*
				 * device space can't exceed 32bit, even if LPAE enable.
				 * LPAE only for memory space.
				 */
				hisi_uart0_base = (u32)fdt64_to_cpu(*(fdt64_t *)data);
		}
#if defined(CONFIG_RTOS_UART_EARLY_INIT)
		uart_init_early(dtb_address, node);
#endif
		hisi_putstr("Uart base = ");
		print_number(hisi_uart0_base);
		hisi_putstr("\n");
	}
}

#ifdef CONFIG_DEBUG_LL
#include <mach/debug_ll.h>
#include <mach/io_map.h>
extern int match_str_in_bootargs(const char *bootargs, const char *target);

void find_debugll(void)
{
	int node;
	char *bootargs = NULL;
	unsigned int size;

	debug_ll = 0;
	node = fdt_subnode_offset(__dtb_addr, 0, "chosen");
	if (node < 0)
		return;

	bootargs = (char *)fdt_getprop(__dtb_addr, node, "bootargs", &size);
	if (bootargs == NULL)
		return;
	if (match_str_in_bootargs(bootargs, "debug_ll=on"))
		debug_ll = 1;
}

void setup_up_debug_ll(void)
{
	unsigned int base = 0;
	unsigned int page_offset = 0;
	unsigned int page_align = 0;

	base = (long)(uintptr_t)output_data;
	base &= ARCH_HISI_DEBUG_BASE_ALIGN;
	base += ARCH_HISI_DEBUG_BASE;

#ifdef CONFIG_ARM_LPAE
	page_align = hisi_uart0_base & LPAE_PAGE_MASK;
#else
	page_align = hisi_uart0_base & NO_LPAE_PAGE_MASK;
#endif
	page_offset = ARCH_HISI_DEBUG_UART_VIRT + page_align;

	*((unsigned int *)(uintptr_t)(base + ARCH_HISI_UART_FLAG_OFFSET)) = debug_ll;
	*((unsigned int *)(uintptr_t)(base + ARCH_HISI_UART_ADDR_OFFSET)) = hisi_uart0_base;
	*((unsigned int *)(uintptr_t)(base + ARCH_HISI_UART_VIRT_OFFSET)) = page_offset;
}
#endif

#ifdef CONFIG_RTOS_CPU_MODE
#include <mach/slave_core.h>

unsigned int hard_bootup_non_secure;
unsigned int soft_bootup_non_secure;

void find_nonsec_info(void)
{
	int node;
	char *bootargs = NULL;
	int size;
	const u32 *data = NULL;

	hard_bootup_non_secure = 0;
	soft_bootup_non_secure = 0;

	node = fdt_node_offset_by_prop_value(__dtb_addr, -1, "compatible",
			"cpu_info,extra_info", sizeof("cpu_info,extra_info"));
	if (node < 0) {
		hisi_putstr("Cannot find cpu extra information and skip.\n");
		return;
	}

	data = fdt_getprop(__dtb_addr, node, "non_secure", &size);
	if (data)
		hard_bootup_non_secure = fdt32_to_cpu(*data);

	node = fdt_subnode_offset(__dtb_addr, 0, "chosen");
	if (node < 0) {
		hisi_putstr("Error: find chosen fail in find_nonsec_info!\n");
		return;
	}

	bootargs = (char *)fdt_getprop(__dtb_addr, node, "bootargs", &size);
	if (bootargs == NULL) {
		hisi_putstr("Error: find chosen fail in bootargs!\n");
		return;
	}

	if (match_str_in_bootargs(bootargs, "non_secure=on")) {
		hard_bootup_non_secure = 1;
	} else if (match_str_in_bootargs(bootargs, "non_secure=off")) {
		hard_bootup_non_secure = 0;
		return;
	} else {
		return;
	}

	/*
	 * soft_bootup=non_secure only used based on non_secure=on .
	 */
	if (match_str_in_bootargs(bootargs, "soft_bootup=non_secure")) {
		soft_bootup_non_secure = 1;
	} else {
		soft_bootup_non_secure = 0;
	}
}

void set_nonsec_info(void)
{
	unsigned int base = 0;

	base = 0x00000;

	if ((hard_bootup_non_secure == 1) && (soft_bootup_non_secure == 0)) {
		io_write((base + HISI_MASTERCORE_NONSEC), 1);
		io_write((base + HISI_SLAVECORE_NONSEC), 1);
	} else if ((hard_bootup_non_secure == 1) && (soft_bootup_non_secure == 1)) {
		io_write((base + HISI_MASTERCORE_NONSEC), 0);
		io_write((base + HISI_SLAVECORE_NONSEC), 1);
	} else {
		io_write((base + HISI_MASTERCORE_NONSEC), 0);
		io_write((base + HISI_SLAVECORE_NONSEC), 0);
	}
}
#endif

#ifdef CONFIG_RTOS_SD5651T_NSBOOT
#include <mach/slave_core.h>

unsigned int hard_bootup_nsboot;

void find_nsboot_info(void)
{
	int node;
	char *bootargs = NULL;
	int size;

	hard_bootup_nsboot = 0;

	node = fdt_subnode_offset(__dtb_addr, 0, "chosen");
	if (node < 0) {
		hisi_putstr("Error: find chosen fail in find_nsboot_info!\n");
		return;
	}

	bootargs = (char *)fdt_getprop(__dtb_addr, node, "bootargs", &size);
	if (bootargs == NULL) {
		hisi_putstr("Error: find chosen fail in bootargs!\n");
		return;
	}

	if (match_str_in_bootargs(bootargs, "nsboot=on"))
		hard_bootup_nsboot = 1;
}

void set_nsboot_info(void)
{
	unsigned int base = 0x00000;

	if (hard_bootup_nsboot == 1)
		io_write((base + HISI_MASTERCORE_NONSEC), 1);
	else
		io_write((base + HISI_MASTERCORE_NONSEC), 0);
}
#endif

int arch_decomp_setup(void)
{
#ifdef CONFIG_DEBUG_LL
	find_debugll();
#endif
	find_uart_info(__dtb_addr);

#ifdef CONFIG_RTOS_HAL_INVALIDATE_BTB
	check_rtos_flag(__dtb_addr, output_data);
#endif

#ifdef CONFIG_RTOS_CPU_MODE
	find_nonsec_info();
	set_nonsec_info();
#endif

#ifdef CONFIG_RTOS_SD5651T_NSBOOT
	find_nsboot_info();
	set_nsboot_info();
#endif

#ifdef CONFIG_RTOS_HAL_CHIP_HI5651T_SUPPORT
	hi5651t_bugfix(__dtb_addr, output_data);
#endif

	hisi_putstr("dtb addr = ");
	print_number((unsigned int)(uintptr_t)__dtb_addr);
	putc_hisi('\n');

	return 0;
}
#endif

#define putc(ch)    putc_hisi(ch)

#endif

